/*
 * PaletteCreatorView.java
 */

package palettecreator;

import java.awt.Color;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jdesktop.application.Action;
import org.jdesktop.application.ResourceMap;
import org.jdesktop.application.SingleFrameApplication;
import org.jdesktop.application.FrameView;
import org.jdesktop.application.TaskMonitor;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.Raster;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import javax.imageio.ImageIO;
import javax.swing.Timer;
import javax.swing.Icon;
import javax.swing.JDialog;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.filechooser.FileFilter;

/**
 * The application's main frame.
 */
public class PaletteCreatorView extends FrameView {

    ImageView imageView;
    BufferedImage image;
    
    public PaletteCreatorView(SingleFrameApplication app) {
        super(app);

        initComponents();

	imageView = new ImageView();
	mainPane.setViewportView(imageView);
	imageView.bgColor = mainPane.getBackground();
	selectedColor = currSelectedColor.getBackground();

        // status bar initialization - message timeout, idle icon and busy animation, etc
        ResourceMap resourceMap = getResourceMap();
        int messageTimeout = resourceMap.getInteger("StatusBar.messageTimeout");
        messageTimer = new Timer(messageTimeout, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                statusMessageLabel.setText("");
            }
        });
        messageTimer.setRepeats(false);
        int busyAnimationRate = resourceMap.getInteger("StatusBar.busyAnimationRate");
        for (int i = 0; i < busyIcons.length; i++) {
            busyIcons[i] = resourceMap.getIcon("StatusBar.busyIcons[" + i + "]");
        }
        busyIconTimer = new Timer(busyAnimationRate, new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                busyIconIndex = (busyIconIndex + 1) % busyIcons.length;
                statusAnimationLabel.setIcon(busyIcons[busyIconIndex]);
            }
        });
        idleIcon = resourceMap.getIcon("StatusBar.idleIcon");
        statusAnimationLabel.setIcon(idleIcon);
        progressBar.setVisible(false);

        // connecting action tasks to status bar via TaskMonitor
        TaskMonitor taskMonitor = new TaskMonitor(getApplication().getContext());
        taskMonitor.addPropertyChangeListener(new java.beans.PropertyChangeListener() {
            public void propertyChange(java.beans.PropertyChangeEvent evt) {
                String propertyName = evt.getPropertyName();
                if ("started".equals(propertyName)) {
                    if (!busyIconTimer.isRunning()) {
                        statusAnimationLabel.setIcon(busyIcons[0]);
                        busyIconIndex = 0;
                        busyIconTimer.start();
                    }
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(true);
                } else if ("done".equals(propertyName)) {
                    busyIconTimer.stop();
                    statusAnimationLabel.setIcon(idleIcon);
                    progressBar.setVisible(false);
                    progressBar.setValue(0);
                } else if ("message".equals(propertyName)) {
                    String text = (String)(evt.getNewValue());
                    statusMessageLabel.setText((text == null) ? "" : text);
                    messageTimer.restart();
                } else if ("progress".equals(propertyName)) {
                    int value = (Integer)(evt.getNewValue());
                    progressBar.setVisible(true);
                    progressBar.setIndeterminate(false);
                    progressBar.setValue(value);
                }
            }
        });
    }

    @Action
    public void showAboutBox() {
        if (aboutBox == null) {
            JFrame mainFrame = PaletteCreatorApp.getApplication().getMainFrame();
            aboutBox = new PaletteCreatorAboutBox(mainFrame);
            aboutBox.setLocationRelativeTo(mainFrame);
        }
        PaletteCreatorApp.getApplication().show(aboutBox);
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        mainPanel = new javax.swing.JPanel();
        mainPane = new javax.swing.JScrollPane();
        jPanel4 = new javax.swing.JPanel();
        mainTabBar = new javax.swing.JTabbedPane();
        jPanel1 = new javax.swing.JPanel();
        valSlider = new javax.swing.JSlider();
        jLabel1 = new javax.swing.JLabel();
        jLabel2 = new javax.swing.JLabel();
        hueSlider = new javax.swing.JSlider();
        jLabel3 = new javax.swing.JLabel();
        satSlider = new javax.swing.JSlider();
        valSpinner = new javax.swing.JSpinner();
        hueSpinner = new javax.swing.JSpinner();
        satSpinner = new javax.swing.JSpinner();
        jLabel4 = new javax.swing.JLabel();
        keepColorTextField = new javax.swing.JTextField();
        keepColorEnabled = new javax.swing.JCheckBox();
        jPanel2 = new javax.swing.JPanel();
        Apply = new javax.swing.JButton();
        jLabel5 = new javax.swing.JLabel();
        jPanel3 = new javax.swing.JPanel();
        jLabel6 = new javax.swing.JLabel();
        currSelectedColor = new javax.swing.JPanel();
        chbUseColor = new javax.swing.JCheckBox();
        toleranceSlider = new javax.swing.JSlider();
        toleranceSpinner = new javax.swing.JSpinner();
        jLabel7 = new javax.swing.JLabel();
        menuBar = new javax.swing.JMenuBar();
        javax.swing.JMenu fileMenu = new javax.swing.JMenu();
        openMenuItem = new javax.swing.JMenuItem();
        loadPalette = new javax.swing.JMenuItem();
        saveMenuItem = new javax.swing.JMenuItem();
        javax.swing.JMenuItem exitMenuItem = new javax.swing.JMenuItem();
        javax.swing.JMenu helpMenu = new javax.swing.JMenu();
        javax.swing.JMenuItem aboutMenuItem = new javax.swing.JMenuItem();
        statusPanel = new javax.swing.JPanel();
        javax.swing.JSeparator statusPanelSeparator = new javax.swing.JSeparator();
        statusMessageLabel = new javax.swing.JLabel();
        statusAnimationLabel = new javax.swing.JLabel();
        progressBar = new javax.swing.JProgressBar();

        mainPanel.setName("mainPanel"); // NOI18N

        mainPane.setName("mainPane"); // NOI18N
        mainPane.addMouseWheelListener(new java.awt.event.MouseWheelListener() {
            public void mouseWheelMoved(java.awt.event.MouseWheelEvent evt) {
                mainPaneMouseWheelMoved(evt);
            }
        });
        mainPane.addMouseListener(new java.awt.event.MouseAdapter() {
            public void mousePressed(java.awt.event.MouseEvent evt) {
                mainPaneMousePressed(evt);
            }
        });

        jPanel4.setName("jPanel4"); // NOI18N

        mainTabBar.setName("mainTabBar"); // NOI18N

        jPanel1.setName("jPanel1"); // NOI18N
        jPanel1.setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        valSlider.setMinimum(-100);
        valSlider.setValue(0);
        valSlider.setName("valSlider"); // NOI18N
        valSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                valSliderStateChanged(evt);
            }
        });
        jPanel1.add(valSlider, new org.netbeans.lib.awtextra.AbsoluteConstraints(34, 70, 160, -1));

        org.jdesktop.application.ResourceMap resourceMap = org.jdesktop.application.Application.getInstance(palettecreator.PaletteCreatorApp.class).getContext().getResourceMap(PaletteCreatorView.class);
        jLabel1.setText(resourceMap.getString("jLabel1.text")); // NOI18N
        jLabel1.setName("jLabel1"); // NOI18N
        jPanel1.add(jLabel1, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 11, -1, 20));

        jLabel2.setText(resourceMap.getString("jLabel2.text")); // NOI18N
        jLabel2.setName("jLabel2"); // NOI18N
        jPanel1.add(jLabel2, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 42, 33, 21));

        hueSlider.setMaximum(360);
        hueSlider.setValue(180);
        hueSlider.setName("hueSlider"); // NOI18N
        hueSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                hueSliderStateChanged(evt);
            }
        });
        jPanel1.add(hueSlider, new org.netbeans.lib.awtextra.AbsoluteConstraints(34, 10, 160, -1));

        jLabel3.setText(resourceMap.getString("jLabel3.text")); // NOI18N
        jLabel3.setName("jLabel3"); // NOI18N
        jPanel1.add(jLabel3, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 69, -1, 26));

        satSlider.setMinimum(-100);
        satSlider.setValue(0);
        satSlider.setName("satSlider"); // NOI18N
        satSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                satSliderStateChanged(evt);
            }
        });
        jPanel1.add(satSlider, new org.netbeans.lib.awtextra.AbsoluteConstraints(34, 40, 160, -1));

        valSpinner.setName("valSpinner"); // NOI18N
        valSpinner.setValue(Integer.valueOf(valSlider.getValue()));
        valSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                valSpinnerStateChanged(evt);
            }
        });
        jPanel1.add(valSpinner, new org.netbeans.lib.awtextra.AbsoluteConstraints(200, 73, 60, -1));

        hueSpinner.setModel(new javax.swing.SpinnerNumberModel(180, 0, 360, 1));
        hueSpinner.setName("hueSpinner"); // NOI18N
        hueSpinner.setValue(Integer.valueOf(hueSlider.getValue()));
        hueSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                hueSpinnerStateChanged(evt);
            }
        });
        jPanel1.add(hueSpinner, new org.netbeans.lib.awtextra.AbsoluteConstraints(200, 13, 60, -1));

        satSpinner.setName("satSpinner"); // NOI18N
        satSpinner.setValue(Integer.valueOf(satSlider.getValue()));
        satSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                satSpinnerStateChanged(evt);
            }
        });
        jPanel1.add(satSpinner, new org.netbeans.lib.awtextra.AbsoluteConstraints(200, 43, 60, -1));

        jLabel4.setText(resourceMap.getString("jLabel4.text")); // NOI18N
        jLabel4.setName("jLabel4"); // NOI18N
        jPanel1.add(jLabel4, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 110, -1, -1));

        keepColorTextField.setText(resourceMap.getString("keepColorTextField.text")); // NOI18N
        keepColorTextField.setName("keepColorTextField"); // NOI18N
        jPanel1.add(keepColorTextField, new org.netbeans.lib.awtextra.AbsoluteConstraints(70, 107, 42, -1));

        keepColorEnabled.setSelected(true);
        keepColorEnabled.setText(resourceMap.getString("keepColorEnabled.text")); // NOI18N
        keepColorEnabled.setName("keepColorEnabled"); // NOI18N
        keepColorEnabled.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                keepColorEnabledStateChanged(evt);
            }
        });
        jPanel1.add(keepColorEnabled, new org.netbeans.lib.awtextra.AbsoluteConstraints(115, 105, -1, -1));

        mainTabBar.addTab(resourceMap.getString("jPanel1.TabConstraints.tabTitle"), jPanel1); // NOI18N

        jPanel2.setName("jPanel2"); // NOI18N

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 261, Short.MAX_VALUE)
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 237, Short.MAX_VALUE)
        );

        mainTabBar.addTab(resourceMap.getString("jPanel2.TabConstraints.tabTitle"), jPanel2); // NOI18N

        Apply.setText(resourceMap.getString("Apply.text")); // NOI18N
        Apply.setName("Apply"); // NOI18N
        Apply.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                ApplyActionPerformed(evt);
            }
        });

        jLabel5.setText(resourceMap.getString("jLabel5.text")); // NOI18N
        jLabel5.setName("jLabel5"); // NOI18N

        jPanel3.setName("jPanel3"); // NOI18N
        jPanel3.setLayout(new org.netbeans.lib.awtextra.AbsoluteLayout());

        jLabel6.setText(resourceMap.getString("jLabel6.text")); // NOI18N
        jLabel6.setName("jLabel6"); // NOI18N
        jPanel3.add(jLabel6, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 50, -1, 30));

        currSelectedColor.setBorder(javax.swing.BorderFactory.createBevelBorder(javax.swing.border.BevelBorder.LOWERED));
        currSelectedColor.setName("currSelectedColor"); // NOI18N

        javax.swing.GroupLayout currSelectedColorLayout = new javax.swing.GroupLayout(currSelectedColor);
        currSelectedColor.setLayout(currSelectedColorLayout);
        currSelectedColorLayout.setHorizontalGroup(
            currSelectedColorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 61, Short.MAX_VALUE)
        );
        currSelectedColorLayout.setVerticalGroup(
            currSelectedColorLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 18, Short.MAX_VALUE)
        );

        jPanel3.add(currSelectedColor, new org.netbeans.lib.awtextra.AbsoluteConstraints(43, 20, -1, -1));

        chbUseColor.setText(resourceMap.getString("chbUseColor.text")); // NOI18N
        chbUseColor.setName("chbUseColor"); // NOI18N
        chbUseColor.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                chbUseColorStateChanged(evt);
            }
        });
        jPanel3.add(chbUseColor, new org.netbeans.lib.awtextra.AbsoluteConstraints(110, 20, -1, -1));

        toleranceSlider.setValue(10);
        toleranceSlider.setName("toleranceSlider"); // NOI18N
        toleranceSlider.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                toleranceSliderStateChanged(evt);
            }
        });
        jPanel3.add(toleranceSlider, new org.netbeans.lib.awtextra.AbsoluteConstraints(60, 50, 140, -1));

        toleranceSpinner.setModel(new javax.swing.SpinnerNumberModel(180, 0, 360, 1));
        toleranceSpinner.setName("toleranceSpinner"); // NOI18N
        toleranceSpinner.setValue(Integer.valueOf(toleranceSlider.getValue()));
        toleranceSpinner.addChangeListener(new javax.swing.event.ChangeListener() {
            public void stateChanged(javax.swing.event.ChangeEvent evt) {
                toleranceSpinnerStateChanged(evt);
            }
        });
        jPanel3.add(toleranceSpinner, new org.netbeans.lib.awtextra.AbsoluteConstraints(200, 50, 60, 20));

        jLabel7.setText(resourceMap.getString("jLabel7.text")); // NOI18N
        jLabel7.setName("jLabel7"); // NOI18N
        jPanel3.add(jLabel7, new org.netbeans.lib.awtextra.AbsoluteConstraints(10, 20, -1, 20));

        javax.swing.GroupLayout jPanel4Layout = new javax.swing.GroupLayout(jPanel4);
        jPanel4.setLayout(jPanel4Layout);
        jPanel4Layout.setHorizontalGroup(
            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel4Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                        .addGroup(jPanel4Layout.createSequentialGroup()
                            .addComponent(mainTabBar, javax.swing.GroupLayout.PREFERRED_SIZE, 266, javax.swing.GroupLayout.PREFERRED_SIZE)
                            .addContainerGap())
                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup()
                            .addComponent(Apply)
                            .addGap(108, 108, 108))
                        .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel4Layout.createSequentialGroup()
                            .addComponent(jLabel5)
                            .addGap(100, 100, 100)))
                    .addGroup(jPanel4Layout.createSequentialGroup()
                        .addComponent(jPanel3, javax.swing.GroupLayout.DEFAULT_SIZE, 296, Short.MAX_VALUE)
                        .addContainerGap())))
        );
        jPanel4Layout.setVerticalGroup(
            jPanel4Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel4Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(mainTabBar, javax.swing.GroupLayout.PREFERRED_SIZE, 262, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(Apply)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jLabel5)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel3, javax.swing.GroupLayout.PREFERRED_SIZE, 134, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap(147, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel);
        mainPanel.setLayout(mainPanelLayout);
        mainPanelLayout.setHorizontalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup()
                .addComponent(mainPane, javax.swing.GroupLayout.DEFAULT_SIZE, 720, Short.MAX_VALUE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel4, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
        );
        mainPanelLayout.setVerticalGroup(
            mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup()
                .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(mainPane, javax.swing.GroupLayout.DEFAULT_SIZE, 609, Short.MAX_VALUE)
                    .addComponent(jPanel4, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );

        menuBar.setName("menuBar"); // NOI18N

        fileMenu.setText(resourceMap.getString("fileMenu.text")); // NOI18N
        fileMenu.setName("fileMenu"); // NOI18N

        openMenuItem.setText(resourceMap.getString("openMenuItem.text")); // NOI18N
        openMenuItem.setName("openMenuItem"); // NOI18N
        openMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                openMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(openMenuItem);

        loadPalette.setText(resourceMap.getString("loadPalette.text")); // NOI18N
        loadPalette.setName("loadPalette"); // NOI18N
        loadPalette.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                loadPaletteActionPerformed(evt);
            }
        });
        fileMenu.add(loadPalette);

        saveMenuItem.setText(resourceMap.getString("saveMenuItem.text")); // NOI18N
        saveMenuItem.setName("saveMenuItem"); // NOI18N
        saveMenuItem.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                saveMenuItemActionPerformed(evt);
            }
        });
        fileMenu.add(saveMenuItem);

        javax.swing.ActionMap actionMap = org.jdesktop.application.Application.getInstance(palettecreator.PaletteCreatorApp.class).getContext().getActionMap(PaletteCreatorView.class, this);
        exitMenuItem.setAction(actionMap.get("quit")); // NOI18N
        exitMenuItem.setName("exitMenuItem"); // NOI18N
        fileMenu.add(exitMenuItem);

        menuBar.add(fileMenu);

        helpMenu.setText(resourceMap.getString("helpMenu.text")); // NOI18N
        helpMenu.setName("helpMenu"); // NOI18N

        aboutMenuItem.setAction(actionMap.get("showAboutBox")); // NOI18N
        aboutMenuItem.setName("aboutMenuItem"); // NOI18N
        helpMenu.add(aboutMenuItem);

        menuBar.add(helpMenu);

        statusPanel.setName("statusPanel"); // NOI18N

        statusPanelSeparator.setName("statusPanelSeparator"); // NOI18N

        statusMessageLabel.setName("statusMessageLabel"); // NOI18N

        statusAnimationLabel.setHorizontalAlignment(javax.swing.SwingConstants.LEFT);
        statusAnimationLabel.setName("statusAnimationLabel"); // NOI18N

        progressBar.setName("progressBar"); // NOI18N

        javax.swing.GroupLayout statusPanelLayout = new javax.swing.GroupLayout(statusPanel);
        statusPanel.setLayout(statusPanelLayout);
        statusPanelLayout.setHorizontalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(statusPanelSeparator, javax.swing.GroupLayout.DEFAULT_SIZE, 1042, Short.MAX_VALUE)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addContainerGap()
                .addComponent(statusMessageLabel)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 868, Short.MAX_VALUE)
                .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(statusAnimationLabel)
                .addContainerGap())
        );
        statusPanelLayout.setVerticalGroup(
            statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(statusPanelLayout.createSequentialGroup()
                .addComponent(statusPanelSeparator, javax.swing.GroupLayout.PREFERRED_SIZE, 2, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addGroup(statusPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(statusMessageLabel)
                    .addComponent(statusAnimationLabel)
                    .addComponent(progressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE))
                .addGap(3, 3, 3))
        );

        setComponent(mainPanel);
        setMenuBar(menuBar);
        setStatusBar(statusPanel);
    }// </editor-fold>//GEN-END:initComponents

    private void openMenuItemActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_openMenuItemActionPerformed
    {//GEN-HEADEREND:event_openMenuItemActionPerformed
	openImage();
    }//GEN-LAST:event_openMenuItemActionPerformed

    private void saveMenuItemActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_saveMenuItemActionPerformed
    {//GEN-HEADEREND:event_saveMenuItemActionPerformed
	try
	{
	    saveImage();
	} catch (FileNotFoundException ex)
	{
	    Logger.getLogger(PaletteCreatorView.class.getName()).log(Level.SEVERE, null, ex);
	} catch (IOException ex)
	{
	    Logger.getLogger(PaletteCreatorView.class.getName()).log(Level.SEVERE, null, ex);
	}
    }//GEN-LAST:event_saveMenuItemActionPerformed

    private void hueSliderStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_hueSliderStateChanged
    {//GEN-HEADEREND:event_hueSliderStateChanged
	hueSpinner.setValue(Integer.valueOf(hueSlider.getValue()));
	updateImage();
    }//GEN-LAST:event_hueSliderStateChanged

    private void satSliderStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_satSliderStateChanged
    {//GEN-HEADEREND:event_satSliderStateChanged
	satSpinner.setValue(Integer.valueOf(satSlider.getValue()));
	updateImage();
    }//GEN-LAST:event_satSliderStateChanged

    private void valSliderStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_valSliderStateChanged
    {//GEN-HEADEREND:event_valSliderStateChanged
	valSpinner.setValue(Integer.valueOf(valSlider.getValue()));
	updateImage();
    }//GEN-LAST:event_valSliderStateChanged

    private void keepColorEnabledStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_keepColorEnabledStateChanged
    {//GEN-HEADEREND:event_keepColorEnabledStateChanged
	updateImage();
    }//GEN-LAST:event_keepColorEnabledStateChanged

    private void hueSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_hueSpinnerStateChanged
    {//GEN-HEADEREND:event_hueSpinnerStateChanged
	hueSlider.setValue(Integer.valueOf((Integer)hueSpinner.getValue()));
	updateImage();
    }//GEN-LAST:event_hueSpinnerStateChanged

    private void mainPaneMouseWheelMoved(java.awt.event.MouseWheelEvent evt)//GEN-FIRST:event_mainPaneMouseWheelMoved
    {//GEN-HEADEREND:event_mainPaneMouseWheelMoved
	int rot = evt.getWheelRotation();
	if (rot > 0)
	{
	    imageView.scale += 0.1;
	    imageView.scale = Math.min(imageView.scale, 4.0);
	}
	else
	{
	    imageView.scale -= 0.1;
	    imageView.scale = Math.max(imageView.scale, 0.05);
	}
    }//GEN-LAST:event_mainPaneMouseWheelMoved

    private void ApplyActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_ApplyActionPerformed
    {//GEN-HEADEREND:event_ApplyActionPerformed
	updateImage(true, false);

	int val = (hueSlider.getMinimum() + hueSlider.getMaximum()) / 2;
	hueSlider.setValue(val);

	val = (satSlider.getMinimum() + satSlider.getMaximum()) / 2;
	satSlider.setValue(val);

	val = (valSlider.getMinimum() + valSlider.getMaximum()) / 2;
	valSlider.setValue(val);
    }//GEN-LAST:event_ApplyActionPerformed

    private void mainPaneMousePressed(java.awt.event.MouseEvent evt)//GEN-FIRST:event_mainPaneMousePressed
    {//GEN-HEADEREND:event_mainPaneMousePressed
	if (imageView.image != null && (evt.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0)
        {
            int x = evt.getX();
	    int y = evt.getY();

	    x /= imageView.scale;
	    y /= imageView.scale;

	    int col = imageView.image.getRGB(x, y);
	    System.out.println("" + Integer.toHexString(col));

	    selectedColor = new Color(col);
	    currSelectedColor.setBackground(selectedColor);
        }
    }//GEN-LAST:event_mainPaneMousePressed

    private void toleranceSliderStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_toleranceSliderStateChanged
    {//GEN-HEADEREND:event_toleranceSliderStateChanged
	toleranceSpinner.setValue(Integer.valueOf(toleranceSlider.getValue()));
	updateImage();
    }//GEN-LAST:event_toleranceSliderStateChanged

    private void toleranceSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_toleranceSpinnerStateChanged
    {//GEN-HEADEREND:event_toleranceSpinnerStateChanged
	toleranceSlider.setValue(Integer.valueOf((Integer)toleranceSpinner.getValue()));
	updateImage();
    }//GEN-LAST:event_toleranceSpinnerStateChanged

    private void satSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_satSpinnerStateChanged
    {//GEN-HEADEREND:event_satSpinnerStateChanged
	satSlider.setValue(Integer.valueOf((Integer)satSpinner.getValue()));
	updateImage();
    }//GEN-LAST:event_satSpinnerStateChanged

    private void valSpinnerStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_valSpinnerStateChanged
    {//GEN-HEADEREND:event_valSpinnerStateChanged
	valSlider.setValue(Integer.valueOf((Integer)valSpinner.getValue()));
	updateImage();
    }//GEN-LAST:event_valSpinnerStateChanged

    private void chbUseColorStateChanged(javax.swing.event.ChangeEvent evt)//GEN-FIRST:event_chbUseColorStateChanged
    {//GEN-HEADEREND:event_chbUseColorStateChanged
	updateImage();
    }//GEN-LAST:event_chbUseColorStateChanged

    private void loadPaletteActionPerformed(java.awt.event.ActionEvent evt)//GEN-FIRST:event_loadPaletteActionPerformed
    {//GEN-HEADEREND:event_loadPaletteActionPerformed
	fileChooser.setFileFilter(new FileFilter() {

	    @Override
	    public boolean accept(File f)
	    {
		return f.getName().endsWith(".pal") || f.getName().endsWith(".act") || f.isDirectory();
	    }

	    @Override
	    public String getDescription()
	    {
		return "Photoshop palette (*.act, *.pal)";
	    }
	});
	int result = fileChooser.showOpenDialog(getFrame());
	fileChooser.setFileFilter(null);
	if (result == JFileChooser.APPROVE_OPTION)
	{
	    file = fileChooser.getSelectedFile();

	    try
	    {
		readPalette(file);
		updateImage(true, true);
	    } catch (Exception ex)
	    {
		System.out.println("Error while loading palette");
		return;
	    }
	}
    }//GEN-LAST:event_loadPaletteActionPerformed

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton Apply;
    private javax.swing.JCheckBox chbUseColor;
    private javax.swing.JPanel currSelectedColor;
    private javax.swing.JSlider hueSlider;
    private javax.swing.JSpinner hueSpinner;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JLabel jLabel2;
    private javax.swing.JLabel jLabel3;
    private javax.swing.JLabel jLabel4;
    private javax.swing.JLabel jLabel5;
    private javax.swing.JLabel jLabel6;
    private javax.swing.JLabel jLabel7;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JPanel jPanel3;
    private javax.swing.JPanel jPanel4;
    private javax.swing.JCheckBox keepColorEnabled;
    private javax.swing.JTextField keepColorTextField;
    private javax.swing.JMenuItem loadPalette;
    private javax.swing.JScrollPane mainPane;
    private javax.swing.JPanel mainPanel;
    private javax.swing.JTabbedPane mainTabBar;
    private javax.swing.JMenuBar menuBar;
    private javax.swing.JMenuItem openMenuItem;
    private javax.swing.JProgressBar progressBar;
    private javax.swing.JSlider satSlider;
    private javax.swing.JSpinner satSpinner;
    private javax.swing.JMenuItem saveMenuItem;
    private javax.swing.JLabel statusAnimationLabel;
    private javax.swing.JLabel statusMessageLabel;
    private javax.swing.JPanel statusPanel;
    private javax.swing.JSlider toleranceSlider;
    private javax.swing.JSpinner toleranceSpinner;
    private javax.swing.JSlider valSlider;
    private javax.swing.JSpinner valSpinner;
    // End of variables declaration//GEN-END:variables

    private final Timer messageTimer;
    private final Timer busyIconTimer;
    private final Icon idleIcon;
    private final Icon[] busyIcons = new Icon[15];
    private int busyIconIndex = 0;

    private JDialog aboutBox;
    JFileChooser fileChooser = new JFileChooser();
    File file;

    private void openImage()
    {
	int result = fileChooser.showOpenDialog(getFrame());
	if (result == JFileChooser.APPROVE_OPTION)
	{
	    file = fileChooser.getSelectedFile();

	    try
	    {
		BufferedImage img = ImageIO.read(file);
		imageView.origImage = img;
		imageView.image = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_RGB);
		updateImage();
	    } catch (Exception ex)
	    {
		System.out.println("Error while loading image");
		return;
	    }
	}
    }

    private void saveImage() throws FileNotFoundException, IOException
    {
	int keepColor = Integer.parseInt(keepColorTextField.getText(), 16);
	boolean useKeepColor = keepColorEnabled.isSelected();
	
	int result = fileChooser.showSaveDialog(getFrame());
	if (result == JFileChooser.APPROVE_OPTION)
	{
	    FileOutputStream fout = null;
	    fout = new FileOutputStream(fileChooser.getSelectedFile());
	    DataOutputStream paletteStream = new DataOutputStream(fout);

	    BufferedImage img = imageView.origImage;

	    IndexColorModel colModel = ((IndexColorModel)img.getColorModel());
	    int[] indexTable = new int[colModel.getMapSize()];
	    colModel.getRGBs(indexTable);

	    float[] hsb = new float[3];
	    for(int i = 0; i < indexTable.length; i++)
	    {
		Color col = new Color(indexTable[i]);

		if ((col.getRGB() & 0xFFFFFF) == (keepColor & 0xFFFFFF) && useKeepColor)
		{
		}
		else
		{
		    hsb = applyCurrHSV(col);
		    col = new Color(Color.HSBtoRGB(hsb[0], hsb[1], hsb[2]));
		}
		
		paletteStream.writeByte(col.getRed());
		paletteStream.writeByte(col.getGreen());
		paletteStream.writeByte(col.getBlue());
	    }

	    fout.close();
	}
    }

    private Color selectedColor;

    private void updateImage()
    {
	updateImage(false, false);
    }

    private void updateImage(boolean changeOrigImage, boolean applyPalette)
    {
	if (imageView.origImage == null)
	    return;
	
	int keepColor = Integer.parseInt(keepColorTextField.getText(), 16);
	boolean useKeepColor = keepColorEnabled.isSelected();
	BufferedImage img = imageView.origImage;
	IndexColorModel colModel = ((IndexColorModel)img.getColorModel());
	int[] indexTable = new int[colModel.getMapSize()];
	colModel.getRGBs(indexTable);
	boolean useColor = chbUseColor.isSelected();
	int tolerance = toleranceSlider.getValue();

	if (applyPalette)
	{
	    for (int i = 0; i < indexTable.length; i++)
	    {
		indexTable[i] = imagePalette[i];
	    }
	}
	else
	{
	    float[] hsb = new float[3];
	    for (int i = 0; i < indexTable.length; i++)
	    {
		Color newCol = new Color(indexTable[i]);

		if ((newCol.getRGB() & 0xFFFFFF) == (keepColor & 0xFFFFFF) && useKeepColor)
		{
		    continue;
		}

		if (useColor && !isColorInRange(newCol, selectedColor, tolerance))
		{
		    continue;
		}

		hsb = applyCurrHSV(newCol);
		indexTable[i] = Color.getHSBColor(hsb[0], hsb[1], hsb[2]).getRGB();
	    }
	}

	IndexColorModel newM = new IndexColorModel(16, colModel.getMapSize(), indexTable, 0, false, 0, 0);
	BufferedImage newImage = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_BYTE_INDEXED, newM);
	Raster r = img.getRaster();
	newImage.setData(r);

	int[] newRgb = newImage.getRGB(0, 0, img.getWidth(), img.getHeight(), null, 0, img.getWidth());
	imageView.image.setRGB(0, 0, img.getWidth(), img.getHeight(), newRgb, 0, img.getWidth());

	if (changeOrigImage)
	{
	    imageView.origImage = newImage;
	}
    }

    private float[] applyCurrHSV(Color newCol)
    {
	float[] hsb = new float[3];
	Color.RGBtoHSB(newCol.getRed(), newCol.getGreen(), newCol.getBlue(), hsb);
	
	hsb[0] += (hueSlider.getValue() - 180.0) / 360.0 + 1;
	hsb[0] %= 1.0;

	hsb[1] += (hsb[1] * satSlider.getValue()) / 100.0;
	hsb[1] = Math.max(hsb[1], 0);
	hsb[1] = Math.min(hsb[1], 1);

	hsb[2] += (hsb[2] * valSlider.getValue()) / 100.0;
	hsb[2] = Math.max(hsb[2], 0);
	hsb[2] = Math.min(hsb[2], 1);

	return hsb;
    }

    private boolean isColorInRange(Color color, Color baseColor, int tolerance)
    {
	float[] hsb = new float[3];
	Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
	float colH = hsb[0];
	Color.RGBtoHSB(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), hsb);
	float baseH = hsb[0];

	return (int)(Math.abs((colH - baseH)) * 100) <= tolerance;

//	boolean rInRange = (Math.abs(color.getRed() - baseColor.getRed()) * 100 / 255) < tolerance;
//	boolean gInRange = (Math.abs(color.getGreen() - baseColor.getGreen()) * 100 / 255) < tolerance;
//	boolean bInRange = (Math.abs(color.getBlue() - baseColor.getBlue()) * 100 / 255) < tolerance;
//
//	return rInRange && gInRange && bInRange;
    }

    int[] imagePalette = new int[256];

    private void readPalette(File file) throws FileNotFoundException, IOException
    {
	FileInputStream fStream = new FileInputStream(file);

	byte[] buf = new byte[3];
	int i = 0;
	while(fStream.read(buf) != -1)
	{
	    Color col = new Color(0xFF & buf[0], 0xFF & buf[1], 0xFF & buf[2], 255);
	    imagePalette[i] = col.getRGB();
	    i++;
	}
	for (int j = i; j < 256; j++)
	{
	    imagePalette[j] = 0;
	}
    }
}